import hashlib
import json
import time
from typing import Union

import requests

from src.utils.AESUtil import AESUtil
from src.utils.DESUtil import DESUtil


class ZhxyClient:
    """智慧校园客户端"""

    APP_KEY = "zhxy-pc"
    TS_KEY = "zhxy-pc"
    APP_SECRET = "U2FsdGVkX18onKMrOWONQ935AYq/siNMc1CNzjZ4OpSX"
    LOGIN_API = "https://zhxygateway.gdip.edu.cn/login"
    MENU_ADDRESS = 'app'

    def __init__(self):
        """初始化"""
        self.token = ''
        self.is_login = False
        self.user_data = {}
        self.session = requests.Session()

    def login(self, username: str, password: str) -> tuple[bool, str]:
        """
        登录
        Args:
            username: 学号
            password: 密码

        Returns:
            - flag 是否成功
            - msg 成功或失败信息
        """
        if self.is_login:
            return True, "已经登陆了！"
        # 产生时间戳和加密key
        timestamp = ZhxyClient.generate_timestamp()
        key = ZhxyClient.generate_key(ZhxyClient.APP_SECRET)
        # 产生登陆数据包
        payload = {
            "userName":username,
            "passWord":ZhxyClient.encrypt_password(password, timestamp=timestamp),
            "passWordType":"1"
        }
        # 加密数据包并生成请求头
        payload = ZhxyClient.encrypt_data(payload, key)
        headers = ZhxyClient.generate_header(payload=payload, is_prod=True, timestamp=timestamp)
        # 登陆
        try:
            res = self.session.post(url=ZhxyClient.LOGIN_API, data=payload, headers=headers)
            res = res.json()
            if res['code'] == '0':
                data = json.loads(AESUtil.decrypt(res['data'], key))
                self.token = data['token']
                self.user_data = json.loads(ZhxyClient.decrypt_data(data['user'], ZhxyClient.generate_key(self.token)))
                self.is_login = True
                return True, "登陆成功！"
            return False, res['message']
        except Exception as e:
            return False, str(e)

    def post(self, url: str, payload: Union[str | dict], menu_address: str = MENU_ADDRESS) -> tuple[bool, str]:
        """
        发送post请求
        Args:
            url: 请求地址
            payload: 请求体
            menu_address: 菜单地址，默认为"app"
        Returns:
            - flag 是否成功
            - res 成功返回响应内容，失败返回失败信息
        """
        timestamp = ZhxyClient.generate_timestamp()
        key = ZhxyClient.generate_key(token=self.token)

        payload = ZhxyClient.encrypt_data(payload, key)
        headers = ZhxyClient.generate_header(payload=payload, is_prod=True, timestamp=timestamp, menu_address=menu_address, token=self.token)
        
        try:
            res = self.session.post(url=url, data=payload, headers=headers)
            if res.status_code != 200:
                return False, "请求失败"
            # todo
            # 如果返回内容加密，需要解密后再返回
            return True, res.text
        except Exception as e:
            return False, str(e)

    def get_user_id(self) -> str:
        """
        获取用户id
        Returns:
            用户id字符串
        """
        return self.user_data['userId']

    @staticmethod
    def encrypt_password(password: str, timestamp: str) -> str:
        """
        加密登陆密码
        Args:
            password: 原始密码
            timestamp: 秒级时间戳字符串
        Returns:
            加密密码
        """
        key = f'{ZhxyClient.TS_KEY}{timestamp}{timestamp}{ZhxyClient.TS_KEY}'
        return DESUtil.encrypt(password, key)

    @staticmethod
    def decrypt_password(password: str, timestamp: str) -> str:
        """
        解密登陆密码
        Args:
            password: 加密密码
            timestamp: 秒级时间戳字符串
        Returns:
            原始密码
        """
        key = f'{ZhxyClient.TS_KEY}{timestamp}{ZhxyClient.TS_KEY}'
        return DESUtil.decrypt(password, key)

    @staticmethod
    def generate_header(payload: str, is_prod: bool, timestamp: str,
                        menu_address: str = MENU_ADDRESS, token: str = APP_SECRET) -> dict:
        """
        产生请求头
        Args:
            payload: 请求体
            is_prod: 是否为生产环境
            timestamp: 秒级时间戳
            menu_address: 菜单地址
            token: 用户登陆token
        Returns:
            请求头
        """
        sign_raw = f'{ZhxyClient.APP_KEY}{ZhxyClient.APP_SECRET}{payload}{timestamp}{ZhxyClient.APP_SECRET}{ZhxyClient.APP_KEY}'
        sign = hashlib.md5()
        sign.update(sign_raw.encode("utf-8"))
        r_mode = AESUtil.encrypt(f'{is_prod and 'Y' or 'N'}#{timestamp}', ZhxyClient.generate_key(token=token))
        headers = {
            'user-agent': 'okHttp/4.4',
            "AppKey": ZhxyClient.APP_KEY,
            "Sign": sign.hexdigest().upper(),
            "authorization": token,
            "Request-Mode": r_mode,
            'content-type': 'application/json;charset=UTF-8',
            'menuaddress': menu_address,
            "timestamp": timestamp,
            "x-real-timestamp": timestamp
        }
        return headers

    @staticmethod
    def generate_key(token: str) -> str:
        """
        产生加密解密数据使用的密钥
        Args:
            token: 用户登陆token
        Returns:
            密钥
        """
        key = hashlib.md5()
        key.update(token.encode("utf-8"))
        return key.hexdigest()

    @staticmethod
    def generate_timestamp() -> str:
        """
        生成秒级时间戳字符串
        Returns:
            秒级时间戳字符串
        """
        return str(int(round(time.time() * 1000)))

    @staticmethod
    def encrypt_data(data: str | dict, key: str) -> str:
        """
        加密数据
        Args:
            data: 原始数据
            key: 密钥
        Returns:
            加密数据
        """
        if isinstance(data, dict):
            data = json.dumps(data)
        return AESUtil.encrypt(data, key)

    @staticmethod
    def decrypt_data(data: str, key: str) -> str:
        """
        解密数据
        Args:
            data: 原始数据
            key: 密钥
        Returns:
            解密数据
        """
        return AESUtil.decrypt(data, key)